home *** CD-ROM | disk | FTP | other *** search
/ PC Format (PL) 2008 February / PC_Format_022008.iso / Internet / Mozilla Thunderbird wtyczki / lightning-0.7-tb-win.xpi / js / calItemBase.js < prev    next >
Encoding:
Text File  |  2007-08-15  |  26.4 KB  |  808 lines

  1. /* -*- Mode: javascript; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
  2. /* ***** BEGIN LICENSE BLOCK *****
  3.  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  4.  *
  5.  * The contents of this file are subject to the Mozilla Public License Version
  6.  * 1.1 (the "License"); you may not use this file except in compliance with
  7.  * the License. You may obtain a copy of the License at
  8.  * http://www.mozilla.org/MPL/
  9.  *
  10.  * Software distributed under the License is distributed on an "AS IS" basis,
  11.  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  12.  * for the specific language governing rights and limitations under the
  13.  * License.
  14.  *
  15.  * The Original Code is Oracle Corporation code.
  16.  *
  17.  * The Initial Developer of the Original Code is
  18.  *  Oracle Corporation
  19.  * Portions created by the Initial Developer are Copyright (C) 2004
  20.  * the Initial Developer. All Rights Reserved.
  21.  *
  22.  * Contributor(s):
  23.  *   Vladimir Vukicevic <vladimir.vukicevic@oracle.com>
  24.  *   Mike Shaver <shaver@off.net>
  25.  *   Joey Minta <jminta@gmail.com>
  26.  *   Matthew Willis <lilmatt@mozilla.com>
  27.  *   Daniel Boelzle <daniel.boelzle@sun.com>
  28.  *
  29.  * Alternatively, the contents of this file may be used under the terms of
  30.  * either the GNU General Public License Version 2 or later (the "GPL"), or
  31.  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  32.  * in which case the provisions of the GPL or the LGPL are applicable instead
  33.  * of those above. If you wish to allow use of your version of this file only
  34.  * under the terms of either the GPL or the LGPL, and not to allow others to
  35.  * use your version of this file under the terms of the MPL, indicate your
  36.  * decision by deleting the provisions above and replace them with the notice
  37.  * and other provisions required by the GPL or the LGPL. If you do not delete
  38.  * the provisions above, a recipient may use your version of this file under
  39.  * the terms of any one of the MPL, the GPL or the LGPL.
  40.  *
  41.  * ***** END LICENSE BLOCK ***** */
  42.  
  43. //
  44. // calItemBase.js
  45. //
  46.  
  47. const ICAL = Components.interfaces.calIIcalComponent;
  48. const kHashPropertyBagContractID = "@mozilla.org/hash-property-bag;1";
  49. const kIWritablePropertyBag = Components.interfaces.nsIWritablePropertyBag;
  50. const HashPropertyBag = new Components.Constructor(kHashPropertyBagContractID, kIWritablePropertyBag);
  51.  
  52. function calItemBase() {
  53.     this.mPropertyParams = {};
  54. }
  55.  
  56. calItemBase.prototype = {
  57.     mPropertyParams: null,
  58.     mIsProxy: false,
  59.  
  60.     QueryInterface: function (aIID) {
  61.         if (!aIID.equals(Components.interfaces.nsISupports) &&
  62.             !aIID.equals(Components.interfaces.calIItemBase))
  63.         {
  64.             throw Components.results.NS_ERROR_NO_INTERFACE;
  65.         }
  66.  
  67.         return this;
  68.     },
  69.  
  70.     mHashId: null,
  71.     get hashId() {
  72.         if (this.mHashId === null) {
  73.             var rid = this.recurrenceId;
  74.             var cal = this.calendar;
  75.             // some unused delim character:
  76.             this.mHashId = [encodeURIComponent(this.id),
  77.                             rid ? rid.getInTimezone("UTC").icalString : "",
  78.                             cal ? encodeURIComponent(cal.id) : ""].join("#");
  79.         }
  80.         return this.mHashId;
  81.     },
  82.  
  83.     get id() {
  84.         return this.getProperty("UID");
  85.     },
  86.     set id(uid) {
  87.         this.modify();
  88.         this.mHashId = null; // recompute hashId
  89.         return this.setProperty("UID", uid);
  90.     },
  91.  
  92.     get recurrenceId() {
  93.         return this.getProperty("RECURRENCE-ID");
  94.     },
  95.     set recurrenceId(rid) {
  96.         this.modify();
  97.         this.mHashId = null; // recompute hashId
  98.         return this.setProperty("RECURRENCE-ID", rid);
  99.     },
  100.  
  101.     mParentItem: null,
  102.     get parentItem() {
  103.         if (this.mParentItem)
  104.             return this.mParentItem;
  105.         else
  106.             return this;
  107.     },
  108.     set parentItem(value) {
  109.         if (this.mImmutable)
  110.             throw Components.results.NS_ERROR_OBJECT_IS_IMMUTABLE;
  111.         this.mIsProxy = true;
  112.         this.mParentItem = value;
  113.     },
  114.  
  115.     initializeProxy: function (aParentItem) {
  116.         if (this.mImmutable)
  117.             throw Components.results.NS_ERROR_OBJECT_IS_IMMUTABLE;
  118.  
  119.         if (this.mParentItem != null)
  120.             throw Components.results.NS_ERROR_FAILURE;
  121.  
  122.         this.mParentItem = aParentItem;
  123.         this.mCalendar = aParentItem.mCalendar;
  124.         this.mIsProxy = true;
  125.     },
  126.  
  127.     //
  128.     // calIItemBase
  129.     //
  130.     mImmutable: false,
  131.     get isMutable() { return !this.mImmutable; },
  132.  
  133.     mDirty: false,
  134.     modify: function() {
  135.         if (this.mImmutable)
  136.             throw Components.results.NS_ERROR_OBJECT_IS_IMMUTABLE;
  137.         this.mDirty = true;
  138.     },
  139.  
  140.     ensureNotDirty: function() {
  141.         if (!this.mDirty)
  142.             return;
  143.  
  144.         if (this.mImmutable) {
  145.             dump ("### Something tried to undirty a dirty immutable event!\n");
  146.             throw Components.results.NS_ERROR_OBJECT_IS_IMMUTABLE;
  147.         }
  148.  
  149.         this.setProperty("LAST-MODIFIED", jsDateToDateTime(new Date()));
  150.         this.mDirty = false;
  151.     },
  152.  
  153.     makeItemBaseImmutable: function() {
  154.         if (this.mImmutable)
  155.             throw Components.results.NS_ERROR_OBJECT_IS_IMMUTABLE;
  156.  
  157.         // make all our components immutable
  158.         if (this.mRecurrenceInfo)
  159.             this.mRecurrenceInfo.makeImmutable();
  160.  
  161.         if (this.mOrganizer)
  162.             this.mOrganizer.makeImmutable();
  163.         if (this.mAttendees) {
  164.             for (var i = 0; i < this.mAttendees.length; i++)
  165.                 this.mAttendees[i].makeImmutable();
  166.         }
  167.  
  168.         var e = this.mProperties.enumerator;
  169.         while (e.hasMoreElements()) {
  170.             var prop = e.getNext().QueryInterface(Components.interfaces.nsIProperty);
  171.             var val = prop.value;
  172.  
  173.             if (prop.value instanceof Components.interfaces.calIDateTime) {
  174.                 if (prop.value.isMutable)
  175.                     prop.value.makeImmutable();
  176.             }
  177.         }
  178.  
  179.         if (this.alarmOffset) {
  180.             this.alarmOffset.makeImmutable();
  181.             if (this.alarmLastAck) {
  182.                 this.alarmLastAck.makeImmutable();
  183.             }
  184.         }
  185.  
  186.         this.ensureNotDirty();
  187.         this.mImmutable = true;
  188.     },
  189.  
  190.     hasSameIds: function(that) {
  191.         return (that && this.id == that.id &&
  192.                 (this.recurrenceId == that.recurrenceId || // both null
  193.                  (this.recurrenceId && that.recurrenceId &&
  194.                   this.recurrenceId.compare(that.recurrenceId) == 0)));
  195.     },
  196.  
  197.     // initialize this class's members
  198.     initItemBase: function () {
  199.         var now = jsDateToDateTime(new Date());
  200.  
  201.         this.mProperties = new HashPropertyBag();
  202.  
  203.         this.setProperty("CREATED", now.clone());
  204.         this.setProperty("LAST-MODIFIED", now.clone());
  205.         this.setProperty("DTSTAMP", now);
  206.  
  207.         this.mAttendees = null;
  208.  
  209.         this.mRecurrenceInfo = null;
  210.  
  211.         this.mAttachments = null;
  212.     },
  213.  
  214.     // for subclasses to use; copies the ItemBase's values
  215.     // into m. aNewParent is optional
  216.     cloneItemBaseInto: function (m, aNewParent) {
  217.         this.ensureNotDirty();
  218.  
  219.         m.mImmutable = false;
  220.         m.mIsProxy = this.mIsProxy;
  221.         m.mParentItem = aNewParent || this.mParentItem;
  222.         m.mHashId = this.mHashId;
  223.         m.mCalendar = this.mCalendar;
  224.         if (this.mRecurrenceInfo) {
  225.             m.mRecurrenceInfo = this.mRecurrenceInfo.clone();
  226.             m.mRecurrenceInfo.item = m;
  227.         }
  228.  
  229.         if (this.mOrganizer) {
  230.             m.mOrganizer = this.mOrganizer.clone();
  231.         }
  232.  
  233.         if (this.mAttendees) {
  234.             m.mAttendees = new Array(this.mAttendees.length);
  235.             for (var i = 0; i < this.mAttendees.length; i++)
  236.                 m.mAttendees[i] = this.mAttendees[i].clone();
  237.         }
  238.         else
  239.             m.mAttendees = null;
  240.  
  241.         m.mProperties = Components.classes["@mozilla.org/hash-property-bag;1"].
  242.                         createInstance(Components.interfaces.nsIWritablePropertyBag);
  243.  
  244.         var e = this.mProperties.enumerator;
  245.         while (e.hasMoreElements()) {
  246.             var prop = e.getNext().QueryInterface(Components.interfaces.nsIProperty);
  247.             var val = prop.value;
  248.  
  249.             if (prop.value instanceof Components.interfaces.calIDateTime)
  250.                 val = prop.value.clone();
  251.  
  252.             m.mProperties.setProperty (prop.name, val);
  253.         }
  254.  
  255.         m.mDirty = false;
  256.  
  257.         // these need fixing
  258.         m.mAttachments = this.mAttachments;
  259.  
  260.         // Clone any alarm info that exists, set it to null if it doesn't
  261.         if (this.alarmOffset) {
  262.             m.alarmOffset = this.alarmOffset.clone();
  263.             if (this.alarmLastAck) {
  264.                 m.alarmLastAck = this.alarmLastAck.clone();
  265.             } else {
  266.                 m.alarmLastAck = null;
  267.             }
  268.         } else {
  269.             m.alarmOffset = null;
  270.         }
  271.         m.alarmRelated = this.alarmRelated;
  272.  
  273.         return m;
  274.     },
  275.  
  276.     get lastModifiedTime() {
  277.         this.ensureNotDirty();
  278.         return this.getProperty("LAST-MODIFIED");
  279.     },
  280.  
  281.     get stampTime() {
  282.         var prop = this.getProperty("DTSTAMP");
  283.         if (prop && prop.isValid)
  284.             return prop;
  285.         return this.getProperty("LAST-MODIFIED");
  286.     },
  287.  
  288.     updateStampTime: function() {
  289.         // can't update the stamp time on an immutable event
  290.         if (this.mImmutable)
  291.             return;
  292.  
  293.         this.modify();
  294.         this.setProperty("DTSTAMP", jsDateToDateTime(new Date()));
  295.     },
  296.  
  297.     get unproxiedPropertyEnumerator() {
  298.         return this.mProperties.enumerator;
  299.     },
  300.  
  301.     get propertyEnumerator() {
  302.         if (this.mIsProxy) {
  303.             // nsISimpleEnumerator sucks.  It really, really sucks.
  304.             // The interface is badly defined, it's not clear
  305.             // what happens if you just keep calling getNext() without
  306.             // calling hasMoreElements in between, which seems like more
  307.             // of an informational thing.  An interface with
  308.             // "advance()" which returns true or false, and with "item()",
  309.             // which returns the item the enumerator is pointing to, makes
  310.             // far more sense.  Right now we have getNext() doing both
  311.             // item returning and enumerator advancing, which makes
  312.             // no sense.
  313.             return {
  314.                 firstEnumerator: this.mProperties.enumerator,
  315.                 secondEnumerator: this.mParentItem.propertyEnumerator,
  316.                 handledProperties: { },
  317.  
  318.                 currentItem: null,
  319.  
  320.                 QueryInterface: function(aIID) {
  321.                     if (!aIID.equals(Components.interfaces.nsISimpleEnumerator) ||
  322.                         !aIID.equals(Components.interfaces.nsISupports))
  323.                     {
  324.                         throw Components.results.NS_ERROR_NO_INTERFACE;
  325.                     }
  326.                     return this;
  327.                 },
  328.  
  329.                 hasMoreElements: function() {
  330.                     if (!this.secondEnumerator)
  331.                         return false;
  332.  
  333.                     if (this.firstEnumerator) {
  334.                         var moreFirst = this.firstEnumerator.hasMoreElements();
  335.                         if (moreFirst) {
  336.                             this.currentItem = this.firstEnumerator.getNext();
  337.                             this.handledProperties[this.currentItem.name] = true;
  338.                             return true;
  339.                         }
  340.                         this.firstEnumerator = null;
  341.                     }
  342.  
  343.                     var moreSecond = this.secondEnumerator.hasMoreElements();
  344.                     if (moreSecond) {
  345.                         while (this.currentItem.name in this.handledProperties &&
  346.                                this.secondEnumerator.hasMoreElements())
  347.                         do {
  348.                             this.currentItem = this.secondEnumerator.getNext();
  349.                         } while (this.currentItem.name in this.handledProperties &&
  350.                                  ((this.currentItem = null) == null) && // hack
  351.                                  this.secondEnumerator.hasMoreElements());
  352.  
  353.                         if (!this.currentItem)
  354.                             return false;
  355.  
  356.                         return true;
  357.                     }
  358.  
  359.                     this.secondEnumerator = null;
  360.  
  361.                     return false;
  362.                 },
  363.  
  364.                 getNext: function() {
  365.                     if (!this.currentItem)
  366.                         throw Components.results.NS_ERROR_UNEXPECTED;
  367.  
  368.                     var rval = this.currentItem;
  369.                     this.currentItem = null;
  370.                     return rval;
  371.                 }
  372.             };
  373.         } else {
  374.             return this.mProperties.enumerator;
  375.         }
  376.     },
  377.  
  378.     // The has/get/getUnproxied/set/deleteProperty methods are case-insensitive.
  379.     getProperty: function (aName) {
  380.         aName = aName.toUpperCase();
  381.         try {
  382.             return this.mProperties.getProperty(aName);
  383.         } catch (e) {
  384.             try {
  385.                 if (this.mIsProxy) {
  386.                     return this.mParentItem.getProperty(aName);
  387.                 }
  388.             } catch (e) {}
  389.  
  390.             return null;
  391.         }
  392.     },
  393.  
  394.     getUnproxiedProperty: function (aName) {
  395.         try {
  396.             return this.mProperties.getProperty(aName.toUpperCase());
  397.         } catch (e) { }
  398.         return null;
  399.     },
  400.  
  401.     hasProperty: function (aName) {
  402.         return (this.getProperty(aName.toUpperCase()) != null);
  403.     },
  404.  
  405.     setProperty: function (aName, aValue) {
  406.         if (aName == "LAST-MODIFIED") {
  407.             this.mDirty = false;
  408.         } else {
  409.             this.modify();
  410.         }
  411.         this.mProperties.setProperty(aName.toUpperCase(), aValue);
  412.     },
  413.  
  414.     deleteProperty: function (aName) {
  415.         this.modify();
  416.         try {
  417.             this.mProperties.deleteProperty(aName.toUpperCase());
  418.         } catch (e) { }
  419.     },
  420.  
  421.     getPropertyParameter: function getPP(aPropName, aParamName) {
  422.         return this.mPropertyParams[aPropName][aParamName];
  423.     },
  424.  
  425.     getAttendees: function (countObj) {
  426.         if (!this.mAttendees && this.mIsProxy && this.mParentItem) {
  427.             this.mAttendees = this.mParentItem.getAttendees(countObj);
  428.         }
  429.         if (this.mAttendees) {
  430.             countObj.value = this.mAttendees.length;
  431.             return this.mAttendees.concat([]); // clone
  432.         }
  433.         else {
  434.             countObj.value = 0;
  435.             return [];
  436.         }
  437.     },
  438.  
  439.     getAttendeeById: function (id) {
  440.         var attendees = this.getAttendees({});
  441.         var lowerCaseId = id.toLowerCase();
  442.         for each (var attendee in attendees) {
  443.             // This match must be case insensitive to deal with differing
  444.             // cases of things like MAILTO:
  445.             if (attendee.id.toLowerCase() == lowerCaseId) {
  446.                 return attendee;
  447.             }
  448.         }
  449.         return null;
  450.     },
  451.  
  452.     removeAttendee: function (attendee) {
  453.         this.modify();
  454.         var found = false, newAttendees = [];
  455.         var attendees = this.getAttendees({});
  456.         var attIdLowerCase =attendee.id.toLowerCase();
  457.  
  458.         for (var i = 0; i < attendees.length; i++) {
  459.             if (attendees[i].id.toLowerCase() != attIdLowerCase)
  460.                 newAttendees.push(attendees[i]);
  461.             else
  462.                 found = true;
  463.         }
  464.         if (found)
  465.             this.mAttendees = newAttendees;
  466.         else
  467.             throw Component.results.NS_ERROR_INVALID_ARG;
  468.     },
  469.  
  470.     removeAllAttendees: function() {
  471.         this.modify();
  472.         this.mAttendees = [];
  473.     },
  474.  
  475.     addAttendee: function (attendee) {
  476.         this.modify();
  477.         this.mAttendees = this.getAttendees({});
  478.         this.mAttendees.push(attendee);
  479.     },
  480.  
  481.     mCalendar: null,
  482.     get calendar () {
  483.         return this.mCalendar;
  484.     },
  485.  
  486.     set calendar (v) {
  487.         if (this.mImmutable)
  488.             throw Components.results.NS_ERROR_OBJECT_IS_IMMUTABLE;
  489.         this.mHashId = null; // recompute hashId
  490.         this.mCalendar = v;
  491.     },
  492.  
  493.     mOrganizer: null,
  494.     get organizer() {
  495.         if (!this.mOrganizer && this.mIsProxy && this.mParentItem) {
  496.             return this.mParentItem.organizer;
  497.         }
  498.         else
  499.             return this.mOrganizer;
  500.     },
  501.  
  502.     set organizer(v) {
  503.         this.modify();
  504.         this.mOrganizer = v;
  505.     },
  506.  
  507.     /* MEMBER_ATTR(mIcalString, "", icalString), */
  508.     get icalString() {
  509.         throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
  510.     },
  511.  
  512.     set icalString() {
  513.         throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
  514.     },
  515.  
  516.     // All of these property names must be in upper case for isPropertyPromoted to
  517.     // function correctly. The has/get/getUnproxied/set/deleteProperty interfaces
  518.     // are case-insensitive, but these are not.
  519.     itemBasePromotedProps: {
  520.         "CREATED": true,
  521.         "UID": true,
  522.         "LAST-MODIFIED": true,
  523.         "SUMMARY": true,
  524.         "PRIORITY": true,
  525.         "METHOD": true,
  526.         "STATUS": true,
  527.         "CLASS": true,
  528.         "DTSTAMP": true,
  529.         "X-MOZILLA-GENERATION": true,
  530.         "RRULE": true,
  531.         "EXDATE": true,
  532.         "RDATE": true,
  533.         "ATTENDEE": true,
  534.         "ORGANIZER": true,
  535.         "RECURRENCE-ID": true
  536.     },
  537.  
  538.     icsBasePropMap: [
  539.     { cal: "CREATED", ics: "createdTime" },
  540.     { cal: "LAST-MODIFIED", ics: "lastModified" },
  541.     { cal: "DTSTAMP", ics: "stampTime" },
  542.     { cal: "UID", ics: "uid" },
  543.     { cal: "SUMMARY", ics: "summary" },
  544.     { cal: "PRIORITY", ics: "priority" },
  545.     { cal: "STATUS", ics: "status" },
  546.     { cal: "CLASS", ics: "icalClass" },
  547.     { cal: "RECURRENCE-ID", ics: "recurrenceId" } ],
  548.  
  549.     mapPropsFromICS: function(icalcomp, propmap) {
  550.         for (var i = 0; i < propmap.length; i++) {
  551.             var prop = propmap[i];
  552.             var val = icalcomp[prop.ics];
  553.             if (val != null && val != ICAL.INVALID_VALUE)
  554.                 this.setProperty(prop.cal, val);
  555.         }
  556.     },
  557.  
  558.     mapPropsToICS: function(icalcomp, propmap) {
  559.         for (var i = 0; i < propmap.length; i++) {
  560.             var prop = propmap[i];
  561.             var val = this.getProperty(prop.cal);
  562.             if (val != null && val != ICAL.INVALID_VALUE)
  563.                 icalcomp[prop.ics] = val;
  564.         }
  565.     },
  566.  
  567.     setItemBaseFromICS: function (icalcomp) {
  568.         this.modify();
  569.  
  570.         this.mapPropsFromICS(icalcomp, this.icsBasePropMap);
  571.  
  572.         for (var attprop = icalcomp.getFirstProperty("ATTENDEE");
  573.              attprop;
  574.              attprop = icalcomp.getNextProperty("ATTENDEE")) {
  575.             
  576.             var att = new CalAttendee();
  577.             att.icalProperty = attprop;
  578.             this.addAttendee(att);
  579.         }
  580.  
  581.         var orgprop = icalcomp.getFirstProperty("ORGANIZER");
  582.         if (orgprop) {
  583.             var org = new CalAttendee();
  584.             org.icalProperty = orgprop;
  585.             org.isOrganizer = true;
  586.             this.mOrganizer = org;
  587.         }
  588.         
  589.         var gen = icalcomp.getFirstProperty("X-MOZILLA-GENERATION");
  590.         if (gen)
  591.             this.mGeneration = parseInt(gen.value);
  592.  
  593.         // find recurrence properties
  594.         var rec = null;
  595.         for (var recprop = icalcomp.getFirstProperty("ANY");
  596.              recprop;
  597.              recprop = icalcomp.getNextProperty("ANY"))
  598.         {
  599.             var ritem = null;
  600.             if (recprop.propertyName == "RRULE" ||
  601.                 recprop.propertyName == "EXRULE")
  602.             {
  603.                 ritem = new CalRecurrenceRule();
  604.             } else if (recprop.propertyName == "RDATE" ||
  605.                        recprop.propertyName == "EXDATE")
  606.             {
  607.                 ritem = new CalRecurrenceDate();
  608.             } else {
  609.                 continue;
  610.             }
  611.  
  612.             ritem.icalProperty = recprop;
  613.  
  614.             if (!rec) {
  615.                 rec = new CalRecurrenceInfo();
  616.                 rec.item = this;
  617.             }
  618.  
  619.             rec.appendRecurrenceItem(ritem);
  620.         }
  621.         this.mRecurrenceInfo = rec;
  622.  
  623.         var alarmComp = icalcomp.getFirstSubcomponent("VALARM");
  624.         if (alarmComp) {
  625.             var triggerProp = alarmComp.getFirstProperty("TRIGGER");
  626.             // Really, really old Sunbird/Calendar versions didn't give us a
  627.             // trigger.
  628.             if (!triggerProp) {
  629.                 Components.utils.reportError("No trigger property for alarm on item: "+this.id);
  630.                 // No parsing happens after alarms, so just return
  631.                 return;
  632.             }
  633.             var duration = Components.classes["@mozilla.org/calendar/duration;1"]
  634.                                      .createInstance(Components.interfaces.calIDuration);
  635.             duration.icalString = triggerProp.valueAsIcalString;
  636.             this.alarmOffset = duration;
  637.  
  638.             var related = triggerProp.getParameter("RELATED");
  639.             if (related && related == "END")
  640.                 this.alarmRelated = Components.interfaces.calIItemBase.ALARM_RELATED_END;
  641.             else
  642.                 this.alarmRelated = Components.interfaces.calIItemBase.ALARM_RELATED_START;
  643.  
  644.             var lastAck = alarmComp.getFirstProperty("X-MOZ-LASTACK");
  645.             if (lastAck) {
  646.                 var lastAckTime = Components.classes["@mozilla.org/calendar/datetime;1"]
  647.                                             .createInstance(Components.interfaces.calIDateTime);
  648.                 lastAckTime.icalString = lastAck.valueAsIcalString;
  649.                 this.alarmLastAck = lastAckTime;
  650.             }
  651.  
  652.             var email = alarmComp.getFirstProperty("X-EMAILADDRESS");
  653.             if (email)
  654.                 this.setProperty("alarmEmailAddress", email.value);
  655.         }
  656.     },
  657.  
  658.     importUnpromotedProperties: function (icalcomp, promoted) {
  659.         for (var prop = icalcomp.getFirstProperty("ANY");
  660.              prop;
  661.              prop = icalcomp.getNextProperty("ANY")) {
  662.             if (!promoted[prop.propertyName]) {
  663.                 this.setProperty(prop.propertyName, prop.value);
  664.                 var param = prop.getFirstParameterName();
  665.                 while (param) {
  666.                     if (!(prop.propertyName in this.mPropertyParams)) {
  667.                         this.mPropertyParams[prop.propertyName] = {};
  668.                     }
  669.                     this.mPropertyParams[prop.propertyName][param] = prop.getParameter(param);
  670.                     param = prop.getNextParameterName();
  671.                 }
  672.             }
  673.         }
  674.     },
  675.  
  676.     // This method is case-insensitive.
  677.     isPropertyPromoted: function (name) {
  678.         return (this.itemBasePromotedProps[name.toUpperCase()]);
  679.     },
  680.  
  681.     get icalComponent() {
  682.         throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
  683.     },
  684.  
  685.     fillIcalComponentFromBase: function (icalcomp) {
  686.         // Make sure that the LMT and ST are updated
  687.         this.updateStampTime();
  688.         this.ensureNotDirty();
  689.  
  690.         this.mapPropsToICS(icalcomp, this.icsBasePropMap);
  691.  
  692.         if (this.mOrganizer)
  693.             icalcomp.addProperty(this.mOrganizer.icalProperty);
  694.         var attendees = this.getAttendees({});
  695.         if (attendees.length > 0) {
  696.           for (var i = 0; i < attendees.length; i++) {
  697.             icalcomp.addProperty(attendees[i].icalProperty);
  698.           }
  699.         }
  700.  
  701.         if (this.mGeneration) {
  702.             var genprop = icalProp("X-MOZILLA-GENERATION");
  703.             genprop.value = String(this.mGeneration);
  704.             icalcomp.addProperty(genprop);
  705.         }
  706.  
  707.         if (this.mRecurrenceInfo) {
  708.             var ritems = this.mRecurrenceInfo.getRecurrenceItems({});
  709.             for (i in ritems) {
  710.                 icalcomp.addProperty(ritems[i].icalProperty);
  711.             }
  712.         }
  713.         
  714.         if (this.alarmOffset) {
  715.             const icssvc = Components.classes["@mozilla.org/calendar/ics-service;1"]
  716.                                      .getService(Components.interfaces.calIICSService);
  717.             var alarmComp = icssvc.createIcalComponent("VALARM");
  718.  
  719.             var triggerProp = icssvc.createIcalProperty("TRIGGER");
  720.             triggerProp.valueAsIcalString = this.alarmOffset.icalString;
  721.  
  722.             if (this.alarmRelated == Components.interfaces.calIItemBase.ALARM_RELATED_END)
  723.                 triggerProp.setParameter("RELATED", "END");
  724.  
  725.             alarmComp.addProperty(triggerProp);
  726.  
  727.             if (this.alarmLastAck) {
  728.                 var lastAck = icssvc.createIcalProperty("X-MOZ-LASTACK");
  729.                 lastAck.valueAsIcalString = this.alarmLastAck.icalString;
  730.                 alarmComp.addProperty(lastAck);
  731.             }
  732.  
  733.             // We don't use this, but the ics-spec requires it
  734.             var descProp = icssvc.createIcalProperty("DESCRIPTION");
  735.             descProp.value = "Mozilla Alarm: "+ this.title;
  736.             alarmComp.addProperty(descProp);
  737.  
  738.             var actionProp = icssvc.createIcalProperty("ACTION");
  739.             actionProp.value = "DISPLAY";
  740.  
  741.             if (this.getProperty("alarmEmailAddress")) {
  742.                 var emailProp = icssvc.createIcalProperty("X-EMAILADDRESS");
  743.                 emailProp.value = this.getProperty("alarmEmailAddress");
  744.                 actionProp.value = "EMAIL";
  745.                 alarmComp.addProperty(emailProp);
  746.             }
  747.  
  748.             alarmComp.addProperty(actionProp);
  749.  
  750.             icalcomp.addSubcomponent(alarmComp);
  751.         }
  752.     },
  753.     
  754.     getOccurrencesBetween: function(aStartDate, aEndDate, aCount) {
  755.         throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
  756.     }
  757. };
  758.  
  759. makeMemberAttr(calItemBase, "X-MOZILLA-GENERATION", 0, "generation", true);
  760. makeMemberAttr(calItemBase, "CREATED", null, "creationDate", true);
  761. makeMemberAttr(calItemBase, "SUMMARY", null, "title", true);
  762. makeMemberAttr(calItemBase, "PRIORITY", 0, "priority", true);
  763. makeMemberAttr(calItemBase, "CLASS", "PUBLIC", "privacy", true);
  764. makeMemberAttr(calItemBase, "STATUS", null, "status", true);
  765. makeMemberAttr(calItemBase, "ALARMTIME", null, "alarmTime", true);
  766.  
  767. makeMemberAttr(calItemBase, "mRecurrenceInfo", null, "recurrenceInfo");
  768. makeMemberAttr(calItemBase, "mAttachments", null, "attachments");
  769. makeMemberAttr(calItemBase, "mProperties", null, "properties");
  770.  
  771. function makeMemberAttr(ctor, varname, dflt, attr, asProperty)
  772. {
  773.     // XXX handle defaults!
  774.     var getter = function () {
  775.         if (asProperty)
  776.             return this.getProperty(varname);
  777.         else
  778.             return this[varname];
  779.     };
  780.     var setter = function (v) {
  781.         this.modify();
  782.         if (asProperty)
  783.             return this.setProperty(varname, v);
  784.         else
  785.             return (this[varname] = v);
  786.     };
  787.     ctor.prototype.__defineGetter__(attr, getter);
  788.     ctor.prototype.__defineSetter__(attr, setter);
  789. }
  790.  
  791. //
  792. // helper functions
  793. //
  794.  
  795. function icalFromString(str)
  796. {
  797.     const icssvc = Components.classes["@mozilla.org/calendar/ics-service;1"].
  798.         getService(Components.interfaces.calIICSService);
  799.     return icssvc.parseICS(str);
  800. }
  801.  
  802. function icalProp(kind)
  803. {
  804.     const icssvc = Components.classes["@mozilla.org/calendar/ics-service;1"].
  805.         getService(Components.interfaces.calIICSService);
  806.     return icssvc.createIcalProperty(kind);
  807. }
  808.